package com.michaelbulava.TFS.ui.config;

import com.intellij.openapi.diagnostic.Logger;
import com.michaelbulava.TFS.log.FileLogger;
import com.michaelbulava.TFS.persistence.IdeaPersistenceStoreProvider;
import com.michaelbulava.TFS.ui.helpers.UIHelpers;
import com.microsoft.tfs.core.config.ConnectionInstanceData;
import com.microsoft.tfs.core.config.auth.DefaultTransportRequestHandler;
import com.microsoft.tfs.core.config.httpclient.ConfigurableHTTPClientFactory;
import com.microsoft.tfs.core.credentials.CachedCredentials;
import com.microsoft.tfs.core.credentials.CredentialsManager;
import com.microsoft.tfs.core.credentials.CredentialsManagerFactory;
import com.microsoft.tfs.core.httpclient.*;
import com.microsoft.tfs.core.httpclient.cookie.CookiePolicy;
import com.microsoft.tfs.core.httpclient.cookie.CookieSpec;
import com.microsoft.tfs.core.ws.runtime.client.SOAPRequest;
import com.microsoft.tfs.core.ws.runtime.client.SOAPService;
import com.microsoft.tfs.core.ws.runtime.client.TransportRequestHandler;
import com.microsoft.tfs.core.ws.runtime.exceptions.FederatedAuthException;
import com.microsoft.tfs.core.ws.runtime.exceptions.FederatedAuthFailedException;
import com.microsoft.tfs.core.ws.runtime.exceptions.UnauthorizedException;
import com.microsoft.tfs.util.StringHelpers;

import java.net.URISyntaxException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by mbulava on 5/6/2015.
 */
public class UITransportRequestHandler extends DefaultTransportRequestHandler {
    private static final Logger log = FileLogger.getInstance(UITransportRequestHandler.class);
    private final Object runnableLock = new Object();
    private UITransportAuthRunnable dialogRunnable = null;

    public UITransportRequestHandler(ConnectionInstanceData connectionInstanceData, ConfigurableHTTPClientFactory clientFactory) {
        super(connectionInstanceData, clientFactory);
    }

    @Override
    public Status prepareRequest(SOAPService service, SOAPRequest request, AtomicBoolean cancel){
        ConnectionInstanceData connectionInstanceData = getConnectionInstanceData();
        Credentials credentials = connectionInstanceData.getCredentials();

        log.debug(" Preparing request with the credentials: " +
                (credentials == null ? "null" : credentials.getClass().getSimpleName()));
        if ((connectionInstanceData.getCredentials() == null) ||
                (!(connectionInstanceData.getCredentials() instanceof UsernamePasswordCredentials))) {
            return Status.CONTINUE;
        }
        UsernamePasswordCredentials oldCredentials = (UsernamePasswordCredentials)credentials;
        if (StringHelpers.isNullOrEmpty(oldCredentials.getPassword()))
        {
            Credentials newCredentials = getCredentials(new UITransportUsernamePasswordAuthRunnable(connectionInstanceData.getServerURI()
                    , connectionInstanceData.getCredentials()));
            if (newCredentials == null) {
                cancel.set(true);
                return Status.CONTINUE;
            }
            connectionInstanceData.setCredentials(newCredentials);
        }
        getClientFactory().configureClientCredentials(service.getClient(), service.getClient().getState(), connectionInstanceData);
        return Status.CONTINUE;
    }

    public Status handleSuccess(SOAPService service, SOAPRequest request) {
        Header[] cookieHeaders = request.getPostMethod().getResponseHeaders("Set-Cookie");
        ArrayList<Cookie> fedAuthCookies = new ArrayList();
        if (cookieHeaders.length > 0) {
            log.debug(" Request succeeded - Set-Cookie headers found in the response");
        }
        URI uri;
        String domain;
        try {
            uri = request.getPostMethod().getURI();
            domain = uri.getHost();
        } catch (URIException e) {
            log.error("Incorrect URI", e);
            return Status.CONTINUE;
        }
        int port = uri.getPort();
        if (port < 0) {
            if ("https".equalsIgnoreCase(uri.getScheme())) {
                port = 443;
            } else {
                port = 80;
            }
        }
        CookieSpec cookieParser = CookiePolicy.getCookieSpec("rfc2109");
        for (Header cookieHeader : cookieHeaders) {
            log.debug(" " + cookieHeader.getName() + ": " + cookieHeader.getValue());
            try {
                Cookie[] cookies = cookieParser.parse(domain, port, "/", true, cookieHeader);
                for (Cookie cookie : cookies) {
                    if (cookie.getName().startsWith("FedAuth")) {
                        fedAuthCookies.add(cookie);
                    }
                }
            } catch (Exception e) {
                log.warn(MessageFormat.format("Could not parse authentication cookie {0}", new Object[]{cookieHeader.getValue()}), e);
            }
        }
        if (fedAuthCookies.size() > 0) {
            ConnectionInstanceData connectionInstanceData = getConnectionInstanceData();

            CookieCredentials newCredentials = new CookieCredentials((Cookie[])fedAuthCookies.toArray(new Cookie[fedAuthCookies.size()]));
            if ((!(connectionInstanceData.getCredentials() instanceof CookieCredentials)) || (!newCredentials.equals(connectionInstanceData.getCredentials()))) {
                log.debug(" New Cookie Credentials created");


                log.debug("Apply the new Cookie Credentials to the existing client.");
                connectionInstanceData.setCredentials(newCredentials);

                log.debug(" Save the new Cookie Credentials to the existing Client Factory for future clients in this session.");
                getClientFactory().configureClientCredentials(service.getClient(), service.getClient().getState(), connectionInstanceData);

                CredentialsManager credentialsManager = CredentialsManagerFactory.getCredentialsManager(IdeaPersistenceStoreProvider.INSTANCE);
                try {
                    log.debug(" Save the new Cookie Credentials in the IntelliJ secure storage for future sessions.");
                    credentialsManager.setCredentials(new CachedCredentials(uri.toJavaNetUri(), newCredentials));
                } catch (URISyntaxException e) {
                    log.error("Incorrect URI", e);
                }
            }
        }
        return Status.CONTINUE;
    }

    @Override
    public Status handleException(SOAPService service, SOAPRequest request, Exception exception, AtomicBoolean cancel)
    {
        ConnectionInstanceData connectionInstanceData = getConnectionInstanceData();

        log.info("Authentication requested: ", exception);
        if (super.handleException(service, request, exception, cancel) == Status.COMPLETE)
        {
            log.debug("DefaultTransportAuthHandler handled auth exception for us");
            return Status.COMPLETE;
        }
        log.debug("DefaultTransportAuthHandler did not handle auth exception");
        UITransportAuthRunnable dialogRunnable = null;
        if ((exception instanceof FederatedAuthException))
        {
            log.debug(" FederatedAuthException has been raised.");

            //TODO fix this.
            //Messages.showErrorDialog("Still working on Federated Auth", "Not yet implemented");
            //return Status.COMPLETE;
            cleanupSavedCredentials(service.getClient());

            FederatedAuthException fedEx =  (FederatedAuthException)exception;
//            UnauthorizedException e = new UnauthorizedException(fedEx.getServerURI(), fedEx.getCredentials());
            //dialogRunnable = new UITransportFederatedFallbackAuthRunnable(connectionInstanceData.getServerURI(), connectionInstanceData.getCredentials(),);
            dialogRunnable = new UITransportUsernamePasswordAuthRunnable(connectionInstanceData.getServerURI(),
                    connectionInstanceData.getCredentials(), fedEx);
        }
        else
        {
            if (((exception instanceof UnauthorizedException)) && (service.isPromptForCredentials()))
            {
                log.debug(" UnauthorizedException has been raised.");
                dialogRunnable = new UITransportUsernamePasswordAuthRunnable(connectionInstanceData.getServerURI(), connectionInstanceData.getCredentials(), (UnauthorizedException)exception);
            }
            else
            {
                if ((exception instanceof FederatedAuthFailedException))
                {
                    cleanupSavedCredentials(service.getClient());
                    return Status.CONTINUE;
                }
                log.debug(" Unknown authentication type or shouldn't prompt for this service.");
                return Status.CONTINUE;
            }
        }

        log.debug(" Prompt for credentials");
        Credentials credentials = getCredentials(dialogRunnable);
        log.info(" The dialog returned credentials: " +
                (credentials == null ? "null" : credentials.getClass().getSimpleName()));
        if (credentials == null)
        {
            log.warn(" Credentials dialog has been cancelled by the user.");
            cancel.set(true);
            return Status.CONTINUE;
        }
        connectionInstanceData.setCredentials(credentials);
        service.getClient().getState().clearCredentials();
        getClientFactory().configureClientCredentials(service.getClient(), service.getClient().getState(), connectionInstanceData);


        return Status.COMPLETE;
    }

    private void cleanupSavedCredentials(HttpClient client)
    {
        log.debug(" If any credentials were used they failed. Clean up saved credentials for the host");

        ConnectionInstanceData connectionInstanceData = getConnectionInstanceData();

        client.getState().clearCookies();
        client.getState().clearCredentials();

        CredentialsManager credentialsManager = CredentialsManagerFactory.getCredentialsManager(IdeaPersistenceStoreProvider.INSTANCE);

        credentialsManager.removeCredentials(connectionInstanceData.getServerURI());

        connectionInstanceData.setCredentials(new DefaultNTCredentials());

        getClientFactory().configureClientCredentials(client, client.getState(), connectionInstanceData);

        //Browser.clearSessions();
    }

    private Credentials getCredentials(UITransportAuthRunnable dialogRunnable)
    {
        boolean ownsRunnable = false;
        try
        {
            UITransportAuthRunnable runnable;
            synchronized (this.runnableLock)
            {
                if (this.dialogRunnable == null)
                {
                    this.dialogRunnable = dialogRunnable;
                    ownsRunnable = true;
                }
                runnable = this.dialogRunnable;
            }
            if (ownsRunnable) {
                UIHelpers.runOnUIThread(false, runnable);
            } else {
                while (!runnable.isComplete()) {
//                    if (UIHelpers.getDisplay().getThread() == Thread.currentThread())
//                    {
//                        if (!UIHelpers.getDisplay().readAndDispatch()) {
//                            UIHelpers.getDisplay().sleep();
//                        }
//                    }
//                    else {
                        try
                        {
                            Thread.sleep(100L);
                        }
                        catch (InterruptedException e)
                        {
                            log.warn("Interrupted waiting for credentials dialog", e);
                        }
//                    }
                }
            }
            return runnable.getCredentials();
        }
        finally
        {
            if (ownsRunnable) {
                synchronized (this.runnableLock)
                {
                    this.dialogRunnable = null;
                }
            }
        }
    }
}
